using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using SpoonCore2.Tools;

namespace SpoonCore2.Network
{
	public class SpoonUDPSocket : ISpoonSocket
	{
		private bool _running = true;
		private readonly Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

		private readonly Thread _receiveThread;
		private readonly Thread _dispacheThread;
		private readonly Thread _writeThread;
		private readonly BlockingRingBuffer<BufferEntry> _sendBuffer = new BlockingRingBuffer<BufferEntry>(100);
		private readonly BlockingRingBuffer<BufferEntry> _receiveBuffer = new BlockingRingBuffer<BufferEntry>(100);
		private readonly int _port;

		public event OnReceiveDataHandler OnReceiveData;

		public SpoonUDPSocket(int port)
		{
			_socket.Bind(new IPEndPoint(IPAddress.Any, port));
			_port = ((IPEndPoint)_socket.LocalEndPoint).Port;

			_receiveThread = new Thread(Receive);
			_receiveThread.Start();
			_dispacheThread = new Thread(Dispache);
			_dispacheThread.Start();
			_writeThread = new Thread(Write);
			_writeThread.Start();
		}

		public SpoonUDPSocket()
			: this(0)
		{
		}

		public int Port
		{
			get { return _port; }
		}

		public void Send(IPEndPoint endpoint, byte[] data)
		{
			_sendBuffer.Enqueue(new BufferEntry(endpoint, data));
		}

		private void Write()
		{
			while (_running)
			{
				try
				{
					BufferEntry entry = _sendBuffer.Dequeue();
					_socket.SendTo(entry.Data, entry.EndPoint);
				}
				catch (ObjectDisposedException)
				{ break; }
				catch(SocketException)
				{}
			}
		}

		private void Receive()
		{
			byte[] buffer = new byte[65000];
			SocketFlags flags = SocketFlags.None;
			EndPoint endpoint = new IPEndPoint(IPAddress.Any, 0);

			while (_running)
			{
				try
				{
					IPPacketInformation packetInfo;
					int count = _socket.ReceiveMessageFrom(buffer, 0, buffer.Length, ref flags, ref endpoint, out packetInfo);
					byte[] data = new byte[count];
					Array.Copy(buffer, data, count);
					_receiveBuffer.Enqueue(new BufferEntry((IPEndPoint)endpoint, data));
				}
				catch (SocketException)
				{ }
			}
		}

		private void Dispache()
		{
			while (_running)
			{
				BufferEntry entry = _receiveBuffer.Dequeue();
				if (OnReceiveData != null) OnReceiveData(entry.EndPoint, new BinaryReader(new MemoryStream(entry.Data)));
			}
		}

		private class BufferEntry
		{
			public readonly IPEndPoint EndPoint;
			public readonly byte[] Data;

			public BufferEntry(IPEndPoint endpoint, byte[] data)
			{
				EndPoint = endpoint;
				Data = data;
			}
		}

		public void Dispose()
		{
			_running = false;

			_socket.Close();

			_receiveThread.Abort();
			_receiveThread.Interrupt();
			_receiveThread.Join();

			_dispacheThread.Abort();
			_dispacheThread.Interrupt();
			_dispacheThread.Join();
		}
	}
}